<?php
/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */


/**
 * Smarty {_tablelist} function plugin
 *
 * Type:     function<br>
 * Name:     _tablelits<br>
 * Date:     Oct 9, 2008<br>
 * Purpose:  make an list html table from an array of data<br>
 * Input:<br>
 *         - from = array to loop through
 *         - cols = number of columns, comma separated list of column names
 *                  or array of column names
 *         - inner = inner loop (default "cols": print $loop line by line,
 *                   $loop will be printed column by column otherwise)
 *
 *
 * Examples:
 * <pre>
 * {table from=$data}
 * {table from=$data cols=4}
 * {table from=$data cols="first,second,third"}
 * </pre>
 * @author Alex Yaroshevich <qfox@ya.ru>
 * @version 0.1a
 * @link http://docs.commercial.su/view:smarty {_table}
 * @param array
 * @param Smarty
 * @return string
 */
function smarty_function__table($params, &$smarty)
{
	$template = "bits/tablelist.sma";

	if( !isset( $params['from'] ) || !is_array( $params['from'] ) || empty( $params['from'] ) ) return '<p>_table: no data to show</p>';
	$params['_header'] = smarty_function__tablelist_parse_cols( isset( $params['cols'] ) ? $params['cols'] : join('|',array_keys(reset($params['from']))) );

	$params['spacer'] = isset($params['spacer'])?$params['spacer']:"&mdash;";
	$params['headtype'] = isset($params['headtype']) ? (min(max(intval($params['headtype']),0),3)) : 1; //0-none,1-up,2-bottom,3-both

	$grouping = false;
	if (isset($params['grouping']) && is_array($params['grouping'])) {
		$grouping = $params['grouping'];
		$fields = array_keys(reset($params['from']));

		//$grouping['by'] = explode(',',$grouping['by']);
		foreach ( $grouping['by'] as $k => $cb ) {
			if (in_array( $k, $fields )) {
				if ((is_string($cb) && is_callable("smarty_function__table_sorter_{$cb}"))) {
					$grouping['by'][$k] = call_user_func( "smarty_function__table_sorter_{$cb}", $k );
					continue;
				} elseif (is_callable($cb))
					continue;
			}
			unset( $grouping['by'][$k] );
		}

		foreach ( $grouping['aggregates'] as $k => $cb ) {
			if (in_array( $k, $fields )) {
				if ((is_string($cb) && is_callable("smarty_function__table_aggretator_{$cb}"))) {
					$grouping['aggregates'][$k] = "smarty_function__table_aggretator_{$cb}";
					continue;
				} elseif (is_callable($cb))
					continue;
			}
			unset( $grouping['aggregates'][$k] );
		}

		$grouped = array();
		$items = $params['from'];

		// megasort...
		$sort_func = '';
		foreach ( $grouping['by'] as $cb )
			$sort_func .= sprintf('$x=call_user_func("%s",$a,$b);'."\n".'if($x) return $x;'."\n",$cb);
		if( is_callable($grouping['order']) )
			$sort_func .= sprintf('$x=call_user_func("%s",$a,$b);'."\n".'if($x) return $x;'."\n",$grouping['order']);
		$sort_func .= 'return 0;';
		uasort($items,create_func('$a,$b',$sort_func));

		$empty_row = array_fill_keys($fields, '');
//		foreach ( $fields as $k => $v ) $params['_header'][0][$k]['key'] = $v;
		foreach ( array_keys($grouping['by']) as $k ) {
			unset ( $empty_row[$k] );
			$j = array_search ($k,$fields);
//			foreach (array_keys($params['_header']) as $j)
//				if ( $params['_header'][$j][''] )
			//$params['_header'][0][$j] = null;
			unset ( $params['_header'][0][$j] );
		}
		foreach ( array_keys($empty_row) as $k ) {
			if ( isset( $grouping['aggregates'][$k] ) ) continue;
			$gitemkey = $k;
			break;
		}

		$grouped = array();
		$max_depth = 0;
		foreach ( $items as $id => $item ) {
			$gkey = 'group';
			$pgkey = null;
			$depth = 0;
			foreach ( array_keys($grouping['by']) as $k ) {
				$gkey .= texo::latinate('-'.strip_tags($item[$k]));
				if ($gkey == 'group-') $gkey .= 'empty';

				if(!isset($grouped[$gkey]))
				$grouped[$gkey] = array(
					'type' => 'group',
					'group' => $gitemkey,
					'parent' => $pgkey,
					'depth' => $depth,
					'row' => $empty_row,
					);
				$grouped[$gkey]['row'][$gitemkey] = $item[$k];
				$pgkey = $gkey;
				unset($item[$k]);

				foreach ( $grouping['aggregates'] as $aggrkey => $cb ) {
					$grouped[$gkey]['row'][$aggrkey] = $cb( $gkey . '-' . $aggrkey, $item[$aggrkey] );
				}
				$depth ++;
			}

			$grouped[$id] = array(
				'type' => 'item',
				'parent' => $gkey,
				'depth' => $depth,
				'row' => $item,
				);
			$max_depth = max($max_depth,$depth);
		}

		if ( isset( $params['table_row_handler'] ) && is_callable( $params['table_row_handler'] ) )
			foreach ( array_keys($grouped) as $id )
				$grouped[$id]['row'] = call_user_func( $params['table_row_handler'], $grouped[$id]['row'], $grouped[$id] );

		$params['from'] = $grouped;
		$params['max_depth'] = $max_depth;

		$template = "bits/tablegroupedlist.sma";
	} else {
		if ( isset( $params['table_row_handler'] ) && is_callable( $params['table_row_handler'] ) )
			foreach ( array_keys($params['from']) as $id )
				$params['from'][$id] = call_user_func( $params['table_row_handler'], $params['from'][$id], array() );
	}
//	ob_end_clean();var_dump(array('items' => $grouped, 'grouped' => $grouped, 'groupsort'=>$uksort_func,'header'=>$params['_header'],'fields' => $fields, 'grouping' => $grouping, 'items' => $items) );die;

	$_columns = 0;
	if( !is_null( $params['_header'] ) )
		foreach( $params['_header'][0] as $col ) $_columns += $col['columns'];
	elseif( !empty($params['from']) )
	{
//		if( $params['headtype'] )
//			$params['_header'] = array_keys(reset($params['from']));
		$_columns = sizeof(reset($params['from']));
	}
	$params['_columns'] = $_columns; // count

	$params['row_class_callback'] = @$params['table_row_class'];
	//ob_end_clean();var_dump(array('grouped' => $grouped, 'groupsort'=>$uksort_func,'columns'=>$_columns/*,'header'=>$params['_header']*/,'fields' => $fields, 'grouping' => $grouping, 'items' => $items) );die;
	
	$_r = view::instance()->display( $template, $params );
	
	return $_r;
}

function smarty_function__table_aggretator_sum($key, $v)
{
	static $data; if (!isset($data)) $data = array();
	$data[$key] = bcadd( (isset($data[$key]) ? $data[$key] : 0), $v );
	return $data[$key];
}

function smarty_function__table_aggretator_count($key, $v)
{
	static $data; if (!isset($data)) $data = array();
	$data[$key] = (isset($data[$key]) ? $data[$key] : 0) + !is_null($v);
	return $data[$key];
}

function smarty_function__table_aggretator_avg($key, $v)
{
	static $data; if (!isset($data)) $data = array();
	$data['sum'][$key] = bcadd( (isset($data['sum'][$key]) ? $data['sum'][$key] : 0), $v );
	$data['count'][$key] = (isset($data['count'][$key]) ? $data['count'][$key] : 0) + !is_null($v);
	return $data['count'][$key] > 0 ? bcdiv( $data['sum'][$key], $data['count'][$key] ) : '—';
}

function smarty_function__table_sorter_str($key)
{
	return create_func('$a,$b',sprintf('return strcoll($a["%1$s"],$b["%1$s"]);',$key));
}


function smarty_function__tablelist_parse_cols( $cols )
{
	if( !is_string( $cols ) && !is_array( $cols ) ) return null;
	if( is_string( $cols ) )
		$cols = smarty_function__tablelist_fetch_cols( $cols );
	else {
//		foreach(  ) { }
	}
	
	smarty_function__tablelist_flat_cols( $cols );
	smarty_function__tablelist_calculate_depth( $cols );
	smarty_function__tablelist_calculate_span( $cols );
	
//	ini_set( 'xdebug.var_display_max_depth', '10' );var_dump( $cols );die;
	return smarty_function__tablelist_build_header( $cols );
}

function smarty_function__tablelist_fetch_cols( $cols, $depth = 0 )
{
	$_r = array( );
	
	while( strlen( $cols ) )
	{
		$stop = false;
	        $_col = array( );
		$_col['text'] = "";
		$_col['depth'] = $depth;
	
// parser block
	for( $l=mb_strlen($cols),$i=0; !$stop&&$i<$l; $i++)
	{
		$ch = mb_substr( $cols, $i, 1 );
		switch( $ch ) {
		case '|': $stop = true; break;
		case ']': die( "table columns parse error: unclosed parenthesis ], cols: \"".$cols."\", pos: ".$i ); break;
		case '[':
			$_sub = mb_substr( $cols, $i+1 );
			$pj = 1;
			$chj = "\0";
			// parenthesis validation.
			for( $lj = mb_strlen($_sub),$j=0,$pj=1; $pj&&$j<=$lj; $chj=mb_substr($_sub,++$j,1) )
				$pj += ($chj===']') ? (-1) : ( ($chj==='[') ? 1 : 0 );
			
			// parenthesis parse error. errors with unclosed parenthesis. like "[asdasd" or "...[asdasd[a|z]|qwe[v|f]"
			if( $pj ) die( "table columns parse error: unclosed parenthesis [, cols: \"".$_sub."\", pos: ".$i );
			
			// erase end of substring
			//var_dump( array( 'sub' => $_sub, 'sub?' => mb_substr( $_sub, 0, $j-1 ), 'pj' => $pj, 'j' => $j, 'len' => $lj ) );
			$_sub = mb_substr( $_sub, 0, $j-1 );
			$lj = mb_strlen( $_sub );
			
			$_col['span'] = smarty_function__tablelist_fetch_cols( $_sub, $depth+1 );
			$i+=$lj+1;
			break;
		default: $_col['text'] .= $ch; break;
		}
	}
// end of parser
	
		if( mb_strpos( $_col['text'], ':' ) )
		{
			$options = explode( ':', $_col['text'] );
			//list( $_col['text'], $_col['width'] ) = $options;
			$_col['text'] = array_shift( $options );
			$_col['width'] = array_shift( $options );
			
			foreach( array( ) as $flag ) // flags (?)
			{
				$flagKey = array_search( $flag, $options );
				unset( $options[$flagKey] );
				$_col[ $flag ] = !$flagKey;
			}
			
			parse_str( join( '&', explode( ':', str_replace( '&', "\1", join( ':', $options ) ) ) ), $options );
			//bc::log( $options );
			foreach( array( 'class', 'href', 'title', 'tag', 'after', 'before' ) as $property )
			if( isset( $options[$property] ) )
				$_col[$property] = str_replace( "\1", '&', $options[$property] );
		}
		
		$_r[] = $_col;
		$cols = mb_substr( $cols, $i );
	}
	
	return $_r;
}

function smarty_function__tablelist_calculate_span( &$cols )
{
	$_fc = (reset($cols)); // first column
	for( $depth=$_fc['max_depth']; $depth>=0; $depth-- )
	foreach( array_keys( $cols ) as $k )
	if( $cols[$k]['depth'] == $depth )
	{
		$_col =& $cols[$k]; // current column
		$_col['columns'] = 1; // spanned columns count
		
		// if column have spanned - !
		
		if( isset($_col['span']) && is_array($_col['span']) && !empty($_col['span']) )
		{
			$_col['columns'] = 0;
			//smarty_function__tablelist_calculate_span( $_col['span'] );
			foreach( $_col['span'] as $_subcol ) // spanned columns array
				$_col['columns'] += $_subcol['columns'];
		}
	}
	
	for( $depth=0,$l=$_fc['max_depth']; $depth<=$l; $depth++ )
	foreach( array_keys( $cols ) as $k )
	if( $cols[$k]['depth'] == $depth )
	{
		$parentUpperRows = isset( $cols[$k]['parent']['upperrows'] ) ? $cols[$k]['parent']['upperrows'] : 0;
		$_rx = floor( (($l+1-$parentUpperRows)/$cols[$k]['weight']) );
		$cols[$k]['rows'] = $_rx;
		$cols[$k]['upperrows'] = $parentUpperRows+$_rx;
	}
}

function smarty_function__tablelist_calculate_depth( &$cols, $depth = 0 )
{
	$max_depth = 0;
	foreach( array_keys($cols) as $k ) {
		$q =& $cols[$k];
		$w = 0;
		$d = 0;
		while( isset( $q['parent'] ) && $q ) {
			$d++;$w--;$q=&$q['parent'];
		}
		$max_depth = max( $d, $max_depth );
		$cols[$k]['max_depth'] =& $max_depth;
		$cols[$k]['depth'] = $d;
		$cols[$k]['weight'] = $w;
	}
	
	// branch depths calculation
	$branch_depths = array();
	foreach( array_keys($cols) as $k )
		$branch_depths[ $cols[$k]['root']['id'] ] = max( $cols[$k]['depth'], @$branch_depths[ $cols[$k]['root']['id'] ] );
	
	foreach( array_keys($cols) as $k )
	if( !$cols[$k]['depth'] )
		$cols[$k]['branch_depth'] = $branch_depths[ $k ] + 1;
	
	// weight calculating
	foreach( array_keys($cols) as $k )
		$cols[$k]['weight'] = (isset($cols[$k]['span']) && is_array($cols[$k]['span']) && !empty($cols[$k]['span']))
			? ($cols[$k]['root']['branch_depth']+$cols[$k]['weight'])
			: (1);
}

function smarty_function__tablelist_flat_cols( &$cols, &$parent = null, &$root = null )
{
	$is_root = is_null( $root );
	if( !isset( $branches ) || is_null( $branches ) )
		if( is_null( $parent ) ) $branches =& $cols;
		else $branches =& $parent['span'];
	
	foreach( array_keys( $branches ) as $k )
	{
		if( !is_null( $parent ) )
			$branches[$k]['parent'] =& $parent;
		
		if( $is_root )
			$root =& $branches[$k];
		else
			$cols[] =& $branches[$k];
		
		$branches[$k]['id'] = ($is_root)?$k:(sizeof($cols)-1);
		$branches[$k]['root'] =& $root;
		
		if( isset( $branches[$k]['span'] ) && is_array( $branches[$k]['span'] ) && !empty( $branches[$k]['span'] ) )
			smarty_function__tablelist_flat_cols( $cols, $branches[$k], $root );
	}
}

function smarty_function__tablelist_build_header( $cols )
{
	$_r = array( );
	foreach( array_keys( $cols ) as $k )
	{
		$_r[ $cols[$k]['depth'] ][ $cols[$k]['id'] ] =& $cols[$k];
	}
	
	//ini_set( 'xdebug.var_display_max_depth', '10' );var_dump( $_r );var_dump( $cols );die;
	
	return $_r;
}

